﻿namespace ImageProcessing
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;

    /// <summary>
    /// Class for the application to hide encrypted string in an image.
    /// </summary>
    public class HideString
    {
        /// <summary>
        /// Caclulates the length of the string.
        /// </summary>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <returns>String length</returns>
        public static int CaclulateMaxStringLength(double width, double height)
        {
            double numberOfPixels = width * height;
            return (int)numberOfPixels;
        }

        /// <summary>
        /// Calculates the new image size.
        /// </summary>
        /// <param name="newX">The new X.</param>
        /// <param name="newY">The new Y.</param>
        /// <param name="stringLength">Length of the string.</param>
        public static void CalculateNewSize(ref int newX, ref int newY, int stringLength)
        {
            newX = (int)Math.Ceiling(Math.Sqrt(stringLength));
            newY = newX;
        }

        /// <summary>
        /// Writes the string.
        /// </summary>
        /// <param name="originalImage">The original image.</param>
        /// <param name="stringToHide">The string to hide.</param>
        /// <param name="password">The password.</param>
        /// <returns>Picture with hidden string.</returns>
        public static ImageStructure[,] WriteString(ImageStructure[,] originalImage, string stringToHide, string password)
        {
            int height = originalImage.GetLength(0);
            int width = originalImage.GetLength(1);

            ImageStructure[,] newImage = new ImageStructure[height, width];

            WriteStringLength(newImage, originalImage, stringToHide.Length);

            byte[] stringBytes = EncryptString(stringToHide, password);
            int stringIndex = 0;

            byte mostSignificant, middle, leastSignificant;
            Random rng = new Random();
            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    if (i == 0 && (j == 0 || j == 1))
                    {
                        continue;
                    }

                    if (stringIndex == stringToHide.Length)
                    {
                        newImage[i, j].R = (byte)((originalImage[i, j].R & 248) | rng.Next(0, 7));
                        newImage[i, j].G = (byte)((originalImage[i, j].G & 248) | rng.Next(0, 7));
                        newImage[i, j].B = (byte)((originalImage[i, j].B & 248) | rng.Next(0, 7));
                        continue;
                    }

                    mostSignificant = (byte)(stringBytes[stringIndex] & 224);
                    mostSignificant = (byte)(mostSignificant >> 5);

                    middle = (byte)(stringBytes[stringIndex] & 28);
                    middle = (byte)(middle >> 2);

                    leastSignificant = (byte)(stringBytes[stringIndex] & 3);
                    stringIndex++;

                    newImage[i, j].R = (byte)((originalImage[i, j].R & 248) | mostSignificant);
                    newImage[i, j].G = (byte)((originalImage[i, j].G & 248) | middle);
                    newImage[i, j].B = (byte)((originalImage[i, j].B & 248) | leastSignificant);
                }
            }

            return newImage;
        }

        /// <summary>
        /// Writes the string.
        /// </summary>
        /// <param name="originalImage">The original image.</param>
        /// <param name="password">The password.</param>
        /// <returns>
        /// Picture with hidden string.
        /// </returns>
        public static string ReadString(ImageStructure[,] originalImage, string password)
        {
            int height = originalImage.GetLength(0);
            int width = originalImage.GetLength(1);

            int stringLength = GetStringLength(originalImage);

            byte[] stringBytes = new byte[stringLength];

            int stringIndex = 0;
            byte mostSignificant, middle, leastSignificant;
            Random rng = new Random();
            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    if (i == 0 && (j == 0 || j == 1))
                    {
                        continue;
                    }

                    if (stringIndex == stringLength)
                    {
                        i = height;
                        break;
                    }

                    mostSignificant = (byte)((originalImage[i, j].R & 7) << 5);
                    middle = (byte)((originalImage[i, j].G & 7) << 2);
                    leastSignificant = (byte)(originalImage[i, j].B & 3);

                    stringBytes[stringIndex] = (byte)(mostSignificant | middle | leastSignificant);
                    stringIndex++;
                }
            }

            return DecryptString(stringBytes, password);
        }

        /// <summary>
        /// Encrypts the string.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="password">The password.</param>
        /// <returns>Encrypted string.</returns>
        private static byte[] EncryptString(string text, string password)
        {
            char[] stringArray = text.ToCharArray();
            char[] passwordArray = password.ToCharArray();
            int passIndex = 0;
            byte[] stringBytes = new byte[stringArray.Length];

            for (int i = 0; i < text.Length; i++)
            {
                stringBytes[i] = (byte)(stringArray[i] ^ passwordArray[passIndex]);
                passIndex++;
                if (passIndex == passwordArray.Length)
                {
                    passIndex = 0;
                }
            }

            return stringBytes;
        }

        /// <summary>
        /// Decrypts the string.
        /// </summary>
        /// <param name="stringBytes">The string bytes.</param>
        /// <param name="password">The password.</param>
        /// <returns>Decrypted String</returns>
        private static string DecryptString(byte[] stringBytes, string password)
        {
            char[] stringArray = new char[stringBytes.Length];
            char[] passwordArray = password.ToCharArray();
            int passIndex = 0;
            for (int i = 0; i < stringBytes.Length; i++)
            {
                stringArray[i] = (char)(stringBytes[i] ^ passwordArray[passIndex]);
                passIndex++;
                if (passIndex == passwordArray.Length)
                {
                    passIndex = 0;
                }
            }

            return new string(stringArray);
        }

        /// <summary>
        /// Gets the length of the string.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <returns>String length.</returns>
        private static int GetStringLength(ImageStructure[,] image)
        {
            byte six = 0, five = 0, four = 0, three = 0, two = 0, one = 0;

            six = (byte)(image[0, 1].B & 7);
            five = (byte)(image[0, 1].G & 7);
            four = (byte)(image[0, 1].R & 7);

            three = (byte)(image[0, 0].B & 7);
            two = (byte)(image[0, 0].G & 7);
            one = (byte)(image[0, 0].R & 7);

            int stringLength = ((int)one << 15) | ((int)two << 12) | ((int)three << 9) | ((int)four << 6) | ((int)five << 3) | ((int)six);

            return stringLength;
        }

        /// <summary>
        /// Writes the length of the string.
        /// </summary>
        /// <param name="newImage">The image.</param>
        /// <param name="originalImage">The original image.</param>
        /// <param name="stringLength">Length of the string.</param>
        private static void WriteStringLength(ImageStructure[,] newImage, ImageStructure[,] originalImage, int stringLength)
        {
            byte six = 0, five = 0, four = 0, three = 0, two = 0, one = 0;

            one = (byte)((stringLength & 229376) >> 15);
            two = (byte)((stringLength & 28672) >> 12);
            three = (byte)((stringLength & 3584) >> 9);
            four = (byte)((stringLength & 448) >> 6);
            five = (byte)((stringLength & 56) >> 3);
            six = (byte)(stringLength & 7);

            newImage[0, 0].R = (byte)((originalImage[0, 0].R & 248) | one);
            newImage[0, 0].G = (byte)((originalImage[0, 0].G & 248) | two);
            newImage[0, 0].B = (byte)((originalImage[0, 0].B & 248) | three);

            newImage[0, 1].R = (byte)((originalImage[0, 1].R & 248) | four);
            newImage[0, 1].G = (byte)((originalImage[0, 1].G & 248) | five);
            newImage[0, 1].B = (byte)((originalImage[0, 1].B & 248) | six);
        }
    }
}
